// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package model

import (
	"slices"
	"strings"

	"github.com/GoogleCloudPlatform/khi/pkg/common/filter"
	"github.com/GoogleCloudPlatform/khi/pkg/common/typedmap"
	coreinspection "github.com/GoogleCloudPlatform/khi/pkg/core/inspection"
	coretask "github.com/GoogleCloudPlatform/khi/pkg/core/task"
	"github.com/GoogleCloudPlatform/khi/pkg/model/enum"
	inspectioncore_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/inspectioncore/contract"
)

// FeatureDocumentModel is a model type for generating document docs/en/reference/features.md
type FeatureDocumentModel struct {
	// Features are the list of feature tasks defined in KHI.
	Features []FeatureDocumentElement
}

// FeatureDocumentElement is a model type for a feature task used in FeatureDocumentModel.
type FeatureDocumentElement struct {
	// ID is the unique name of the feature task.
	ID string
	// Name is the human readable name of the feature task.
	Name string
	// Description is the string explain the feature task.
	Description string
	// Forms is the list of information about form inputs that is required from the feature task.
	Forms []FeatureDependentFormElement
	// IndirectQueryDependency is the list of query tasks that is required from this feature task but not the target query task.
	IndirectQueryDependency []FeatureIndirectDependentQueryElement
	// TargetQUeryDependency is the main query task used in this feature task.
	TargetQueryDependency FeatureDependentTargetQueryElement
	// OutputTimelines is the list of timelines(=ParentRelationship type) that can be generated by this feature task.
	OutputTimelines []FeatureOutputTimelineElement
	// AvailableInspctionType is the list of InspectionType that supports this feature task.
	AvailableInspectionTypes []FeatureAvailableInspectionType
}

// FeatureDependentFormElement is a model type for a input form required from a feature task.
type FeatureDependentFormElement struct {
	// ID is the unique name of this form element.
	ID string
	// Label is a human readable short name of this input element.
	Label string
	// Description is a string explaining this form input.
	Description string
}

// FeatureIndirectDependentQueryElement is a model type for query tasks required from a feature task but not the target query task.
type FeatureIndirectDependentQueryElement struct {
	// ID is the unique name of this query task.
	ID string
	// LogTypeLabel is a human readable short name of the log type queried by the query task.
	LogTypeLabel string
	// LogTypeColorCode is the hex color code without the `#` prefix for the log type.
	LogTypeColorCode string
}

// FeatureDependentTargetQueryElement is a model type for a target query task of the feature task.
type FeatureDependentTargetQueryElement struct {
	// ID is the unique name of this query task.
	ID string
	// LogTypeLabel is a human readable short name of the log type queried by the query task.
	LogTypeLabel string
	// LogTypeColorCode is the hex color code without the `#` prefix for the log type.
	LogTypeColorCode string
	// SampleQuery is an example query string used in this query task.
	SampleQuery string
}

// FeatureOutputTimelineElement is a model type for one of relationship type of timelines that can be related to this feature.
type FeatureOutputTimelineElement struct {
	// RelationshipID is the unique name of the relationship type.
	RelationshipID string
	// RelationshipColorCode is the hex color code without the `#` prefix for the relationship type.
	RelationshipColorCode string
	// LongName is the human readable name of the relationship
	LongName string
	// Label is the short name of the timeline. This is also used in the chip on the left side of timelines.
	Label string
	// Description is the string explains the relationship.
	Description string
}

// FeatureAvailableInspectionType is a model type for a InspectionType that supports the feature task.
type FeatureAvailableInspectionType struct {
	// ID is the unique name of the InspectionType.
	ID string
	// Name is the human readable name of the InspectionType.
	Name string
}

// GetFeatureDocumentModel returns the document model for feature tasks from the task server.
func GetFeatureDocumentModel(taskServer *coreinspection.InspectionTaskServer) (*FeatureDocumentModel, error) {
	result := FeatureDocumentModel{}
	features := coretask.Subset(taskServer.RootTaskSet, filter.NewEnabledFilter(inspectioncore_contract.LabelKeyInspectionFeatureFlag, false))
	for _, feature := range features.GetAll() {
		indirectQueryDependencyElement := []FeatureIndirectDependentQueryElement{}
		targetQueryDependencyElement := FeatureDependentTargetQueryElement{}
		targetLogTypeKey := typedmap.GetOrDefault(feature.Labels(), inspectioncore_contract.LabelKeyFeatureTaskTargetLogType, enum.LogTypeUnknown)

		// Get query related tasks in the dependency of this feature.
		queryTasksInDependency, err := getDependentQueryTasks(taskServer, feature)
		if err != nil {
			return nil, err
		}
		for _, queryTask := range queryTasksInDependency {
			logTypeKey := typedmap.GetOrDefault(queryTask.Labels(), inspectioncore_contract.TaskLabelKeyQueryTaskTargetLogType, enum.LogTypeUnknown)
			if targetLogTypeKey != logTypeKey {
				logType := enum.LogTypes[logTypeKey]
				indirectQueryDependencyElement = append(indirectQueryDependencyElement, FeatureIndirectDependentQueryElement{
					ID:               queryTask.UntypedID().String(),
					LogTypeLabel:     logType.Label,
					LogTypeColorCode: strings.TrimLeft(logType.LabelBackgroundColor, "#"),
				})
			} else {
				targetQueryDependencyElement = FeatureDependentTargetQueryElement{
					ID:               queryTask.UntypedID().String(),
					LogTypeLabel:     enum.LogTypes[targetLogTypeKey].Label,
					LogTypeColorCode: strings.TrimLeft(enum.LogTypes[targetLogTypeKey].LabelBackgroundColor, "#"),
					SampleQuery:      typedmap.GetOrDefault(queryTask.Labels(), inspectioncore_contract.TaskLabelKeyQueryTaskSampleQuery, ""),
				}
			}
		}

		formElements := []FeatureDependentFormElement{}
		formTasks, err := getDependentFormTasks(taskServer, feature)
		if err != nil {
			return nil, err
		}
		for _, formTask := range formTasks {
			formElements = append(formElements, FeatureDependentFormElement{
				ID:          formTask.UntypedID().String(),
				Label:       typedmap.GetOrDefault(formTask.Labels(), inspectioncore_contract.TaskLabelKeyFormFieldLabel, ""),
				Description: typedmap.GetOrDefault(formTask.Labels(), inspectioncore_contract.TaskLabelKeyFormFieldDescription, ""),
			})
		}

		outputTimelines := []FeatureOutputTimelineElement{}
		for i := 0; i < enum.EnumParentRelationshipLength; i++ {
			relationshipKey := enum.ParentRelationship(i)
			relationship := enum.ParentRelationships[relationshipKey]

			isRelated := false
			for _, event := range relationship.GeneratableEvents {
				if event.SourceLogType == targetLogTypeKey {
					isRelated = true
					break
				}
			}
			for _, revision := range relationship.GeneratableRevisions {
				if revision.SourceLogType == targetLogTypeKey {
					isRelated = true
					break
				}
			}
			for _, alias := range relationship.GeneratableAliasTimelineInfo {
				if alias.SourceLogType == targetLogTypeKey {
					isRelated = true
					break
				}
			}
			if isRelated {
				outputTimelines = append(outputTimelines, FeatureOutputTimelineElement{
					RelationshipID:        relationship.EnumKeyName,
					RelationshipColorCode: strings.TrimLeft(relationship.LabelBackgroundColor, "#"),
					LongName:              relationship.LongName,
					Label:                 relationship.Label,
					Description:           relationship.Description,
				})
			}
		}

		result.Features = append(result.Features, FeatureDocumentElement{
			ID:                       feature.UntypedID().String(),
			Name:                     typedmap.GetOrDefault(feature.Labels(), inspectioncore_contract.LabelKeyFeatureTaskTitle, ""),
			Description:              typedmap.GetOrDefault(feature.Labels(), inspectioncore_contract.LabelKeyFeatureTaskDescription, ""),
			IndirectQueryDependency:  indirectQueryDependencyElement,
			TargetQueryDependency:    targetQueryDependencyElement,
			Forms:                    formElements,
			OutputTimelines:          outputTimelines,
			AvailableInspectionTypes: getAvailableInspectionTypes(taskServer, feature),
		})

	}
	return &result, nil
}

// getDependentQueryTasks returns the list of query tasks required by the feature task.
func getDependentQueryTasks(taskServer *coreinspection.InspectionTaskServer, featureTask coretask.UntypedTask) ([]coretask.UntypedTask, error) {
	resolved, err := coretask.DefaultTaskGraphResolver.Resolve([]coretask.UntypedTask{featureTask}, taskServer.RootTaskSet.GetAll())
	if err != nil {
		return nil, err
	}
	ts, err := coretask.NewTaskSet(resolved)
	if err != nil {
		return nil, err
	}
	runnable, err := ts.ToRunnableTaskSet()
	if err != nil {
		return nil, err
	}
	return coretask.Subset(runnable, filter.NewEnabledFilter(inspectioncore_contract.TaskLabelKeyIsQueryTask, false)).GetAll(), nil
}

// getDependentFormTasks returns the list of form tasks required by the feature task.
func getDependentFormTasks(taskServer *coreinspection.InspectionTaskServer, featureTask coretask.UntypedTask) ([]coretask.UntypedTask, error) {
	resolved, err := coretask.DefaultTaskGraphResolver.Resolve([]coretask.UntypedTask{featureTask}, taskServer.RootTaskSet.GetAll())
	if err != nil {
		return nil, err
	}
	ts, err := coretask.NewTaskSet(resolved)
	if err != nil {
		return nil, err
	}
	runnable, err := ts.ToRunnableTaskSet()
	if err != nil {
		return nil, err
	}
	return coretask.Subset(runnable, filter.NewEnabledFilter(inspectioncore_contract.TaskLabelKeyIsFormTask, false)).GetAll(), nil
}

// getAvailableInspectionTypes returns the list of information about inspection type that supports this feature.
func getAvailableInspectionTypes(taskServer *coreinspection.InspectionTaskServer, featureTask coretask.UntypedTask) []FeatureAvailableInspectionType {
	result := []FeatureAvailableInspectionType{}
	inspectionTypes := taskServer.GetAllInspectionTypes()
	for _, inspectionType := range inspectionTypes {
		labels, found := typedmap.Get(featureTask.Labels(), inspectioncore_contract.LabelKeyInspectionTypes)
		if found && slices.Contains(labels, inspectionType.Id) {
			result = append(result, FeatureAvailableInspectionType{
				ID:   inspectionType.Id,
				Name: inspectionType.Name,
			})
		}
	}
	return result
}
